
import * as THREE from "three";
import { OrbitControls, } from "three/examples/jsm/controls/OrbitControls.js";
import TWEEN from "@tweenjs/tween.js";
import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer.js";
// 引入必要的库
// ^ 通过EffectComposer可以实现一些后期处理效果。
// 所谓threejs后期处理，就像ps一样，对threejs的渲染结果进行后期处理，比如添加发光效果
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
// ^ 渲染器通道RenderPass的作用是指定后处理对应的相机camera和场景scene
// 引入渲染器通道RenderPass
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
// ^ OutlinePass可以给指定的某个模型对象添加一个高亮发光描边效果。
// 引入OutlinePass通道
import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass.js';

import { titleCase, IsPtInPoly } from "@/utils"
import { colors } from "@/utils/config"
import { createLightPoint } from "@/utils/mesh/lightMesh"
import { createIcon, offsetXY, pointList } from "@/utils/mesh/mapMesh"
import { createLabel } from "@/utils/mesh/LabelMesh"
import { createBar } from "@/utils/mesh/barMesh"

const fun = {
  createLightPoint: createLightPoint,
  createLabel: createLabel,
  createIcon: createIcon,
  createBar: createBar
}

// 重置地图的尺寸
export function setScale(group, size) {
  const { depth } = getDepth(size)
  // 根据最大边长设置缩放倍数，尽量全屏显示
  // 过大会超出屏幕范围，过小全屏效果不明显
  // Math.max(...size) 计算包围盒的最大边长
  const S = 1 / depth;
  // 对模型进行缩放操作，实现全屏效果
  group.scale.set(S, S, S);
  const box = box3(group);
  // 重新设置模型的位置，使模型居中
  group.position.x = group.position.x - box.center.x;
  // group.position.y = group.position.y - box.center.y;
  group.position.y = 0;
  group.position.z = group.position.z - box.center.z;

  // 根据地图大小控制地图显示高度
  const map3D = group.children[0]
  map3D.traverse(obj => {
    // 找到组成地图块的模型
    if (obj.colorName == 'mapMesh') {
      // 设置当前地图的高度
      const shapes = obj.geometry.parameters.shapes;
      const shapeGeometry = new THREE.ExtrudeGeometry(shapes, {
        depth: depth,
        bevelEnabled: false
      });
      obj.geometry = shapeGeometry;
    }
    // 找到组成地图描边的模型
    if (obj.isUpLine) obj.position.z = depth + 0.0001;
  });
}

// 坐标矫正
export const setCenter = map => {
  map.rotation.x = -Math.PI / 2;
  // 创建一个Box3对象，并通过调用setFromObject(map)方法，将map的包围盒信息存储在box变量中
  // box变量现在包含了map对象的边界范围
  const box = new THREE.Box3().setFromObject(map);
  // box.getCenter 获取包围盒的中心点坐标
  const center = box.getCenter(new THREE.Vector3());
  const offset = [0, 0];
  // 相对中心的位置
  map.position.x = map.position.x - center.x - offset[0];
  map.position.z = map.position.z - center.z - offset[1];
};

// ^ 添加光源
export function createLight(scene) {
  // const ambientLight = new THREE.AmbientLight(0xd4e7fd, 4);
  // scene.add(ambientLight);
  // // 平行光
  // let directionalLight = new THREE.DirectionalLight(0xe8eaeb, 1);
  // directionalLight.position.set(0, 0, 200).normalize();
  // scene.add(directionalLight);
  let centerXY = [106.59893798828125, 26.918846130371094]
  //   平行光1
  let directionalLight1 = new THREE.DirectionalLight(0x7af4ff, 1)
  directionalLight1.position.set(...centerXY, 30)
  //   平行光2
  let directionalLight2 = new THREE.DirectionalLight(0x7af4ff, 1)
  directionalLight2.position.set(...centerXY, 30)
  // 环境光
  let ambientLight = new THREE.AmbientLight(0x7af4ff, 1)
  // 将光源添加到场景中
  scene.add(directionalLight1)
  scene.add(directionalLight2)
  scene.add(ambientLight)
}

// ^ 包围盒Box3
// 所谓包围盒Box3，就是一个长方体空间，把模型的所有顶点数据包围在一个最小的长方体空间中，这个最小长方体空间就是该模型的包围盒Box3。
export function box3(mesh) {
  // 包围盒计算模型对象的大小和位置
  const box3 = new THREE.Box3();
  box3.expandByObject(mesh); // 计算模型包围盒
  const size = new THREE.Vector3();
  box3.getSize(size); // 计算包围盒尺寸
  const center = new THREE.Vector3();
  box3.getCenter(center); // 计算包围盒中心坐标
  return { size, center };
}

// ^ 相机
let camera = null
export function createCamera({ width, height }) {
  camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
  camera.position.y = 8;
  camera.position.z = 8;
  return camera;
}

// 用于显示地图的标签的父级div
let labelRenderer = null
export function createLabelRenderer(idName, { width, height }) {
  labelRenderer = new CSS2DRenderer();
  labelRenderer.domElement.style.position = "absolute";
  labelRenderer.domElement.style.top = "0px";
  labelRenderer.domElement.style.pointerEvents = "none";
  labelRenderer.domElement.id = idName + 'label'
  labelRenderer.setSize(width, height);
  document
    .getElementById(idName)
    .appendChild(labelRenderer.domElement);
  return labelRenderer
}

// ^ 渲染器
let renderer = null
export function createRenderer(idName, { width, height }) {
  renderer = new THREE.WebGLRenderer({
    antialias: true // 开启优化锯齿
  });
  renderer.setPixelRatio(window.devicePixelRatio); // 防止输出模糊
  renderer.setSize(width, height); // 设置画布
  renderer.setClearColor(colors.background, 1); // 设置背景颜色和透明度
  renderer.domElement.id = idName + "canvas";
  document.getElementById(idName).appendChild(renderer.domElement);
  return renderer
}

// ^ 轨道控制器
// 轨道控制器可以使得相机围绕目标进行轨道运动。
let controls = null
export function createControls(camera, renderer) {
  controls = new OrbitControls(camera, renderer.domElement);
  controls.update(); // update()函数内会执行camera.lookAt(controls.target)
  return controls
}

// ^ 鼠标交互
export function mouseClick(idName, object3D, camera, outlinePass) {
  let intersect = null;
  // 鼠标点击事件监听
  window.addEventListener("click", event => {
    // 获取互补元素
    const div = document.getElementById(idName);
    // 计算画布的边框
    const rect = div.getBoundingClientRect();
    // 鼠标距离画布左侧边缘的距离占画布宽度的比例
    const ratioX = (event.clientX - rect.left) / div.offsetWidth;
    // 鼠标距离画布顶部边缘的距离占画布高度的比例
    const ratioY = (event.clientY - rect.top) / div.offsetHeight;

    const mouse = new THREE.Vector2();
    // 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)
    mouse.x = ratioX * 2 - 1;
    mouse.y = -ratioY * 2 + 1;
    // THREE.Raycaster可以从指定的原点（起点）沿着指定的方向（射线）发射一条射线。
    // 这条射线可以与场景中的对象进行相交检测，以确定射线是否与对象相交，从而获取与射线相交的对象或交点信息
    const raycaster = new THREE.Raycaster();
    // 通过摄像机和鼠标位置更新射线
    raycaster.setFromCamera(mouse, camera);
    // 计算物体和射线的焦点
    const intersects = raycaster
      // 参数二：设置为true时，若你当前拾取的元素的children中还有子元素，那么这个函数就会检索你下面的子元素
      .intersectObjects(object3D.children, true)
      .filter(item => item.object.type !== "Line" && !item.object.disabled);
    // 对点击的对象进行处理
    if (intersects.length > 0) {
      // 当点击的为网格模型（即地图块）
      if (intersects[0].object.type === "Mesh") {
        // console.log("点击模块", intersects[0].object);
        if (intersect) isRecover(intersect);
        intersect = intersects[0].object.parent;
        isChange(intersect);
      }
      // 当点击的为精灵模型（即标记点）
      if (intersects[0].object.type === "Sprite") {
        console.log("点击标记点", intersects[0].object);
      }
    } else {
      if (intersect) isRecover(intersect);
    }
    // 发生变化
    function isChange(intersect) {
      intersect.children.forEach(item => {
        if (item.name == 'mask') item.material.opacity = 0.4
      })
      const intervalId = setInterval(() => {
        intersect.position.z += 0.1;
        if (intersect.position.z >= 0.5) {
          clearInterval(intervalId);
        }
      }, 50);

      // 对所点击模型的子模型进行处理
      intersect.children.forEach(item => {
        if (item.type === "Mesh") {
          // 记录当前选中物体的状态
          item.initialColor = item.material?.color.clone();
          // item.material?.color?.set(0xfff000);
        }
      });
    }
    // 复原
    function isRecover(intersect) {
      intersect.position.z = 0
      intersect.children.forEach(item => {
        if (item.name == 'mask') item.material.opacity = 0
      })
      const intervalId = setInterval(() => {
        intersect.position.z -= 0.1;
        if (intersect.position.z <= 0) {
          clearInterval(intervalId);
        }
      }, 50);
      intersect.children.forEach(item => {
        if (item.type === "Mesh") {
          // if (item.initialColor) item.material.color.set(item.initialColor);
        }
      });
    }
  });
}

// ^ 获取点击模型
export function getIntersects(idName, scene, camera) {
  // 获取互补元素
  const div = document.getElementById(idName);
  // 计算画布的边框
  const rect = div.getBoundingClientRect();
  // 鼠标距离画布左侧边缘的距离占画布宽度的比例
  const ratioX = (event.clientX - rect.left) / div.offsetWidth;
  // 鼠标距离画布顶部边缘的距离占画布高度的比例
  const ratioY = (event.clientY - rect.top) / div.offsetHeight;

  const mouse = new THREE.Vector2();
  // 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)
  mouse.x = ratioX * 2 - 1;
  mouse.y = -ratioY * 2 + 1;
  // THREE.Raycaster可以从指定的原点（起点）沿着指定的方向（射线）发射一条射线。
  // 这条射线可以与场景中的对象进行相交检测，以确定射线是否与对象相交，从而获取与射线相交的对象或交点信息
  const raycaster = new THREE.Raycaster();
  // 通过摄像机和鼠标位置更新射线
  raycaster.setFromCamera(mouse, camera);
  // 计算物体和射线的焦点
  const intersects = raycaster
    // 参数二：设置为true时，若你当前拾取的元素的children中还有子元素，那么这个函数就会检索你下面的子元素
    .intersectObjects(scene.children, true)
    .filter(item => (item.object.type == "Mesh" || item.object.type == "Sprite") && !item.object.disabled);
  return intersects;
}

export function createOutlinePass(scene, camera, { width, height }) {
  // todo 3.OutlinePass第一个参数v2的尺寸和canvas画布保持一致
  const outlinePass = new OutlinePass(new THREE.Vector2(width, height), scene, camera);
  // todo three.js场景中有多个模型的话，你希望给哪个模型对象设置发光描边效果，就可以通过OutlinePass的选择对象属性.selectedObjects设置。
  // outlinePass.selectedObjects = [mesh]
  // 模型描边颜色，默认白色
  outlinePass.visibleEdgeColor.set(0xffffff);
  // 高亮发光描边厚度
  outlinePass.edgeThickness = 4.0;
  // 高亮描边发光强度
  outlinePass.edgeStrength = 6;
  // 模型闪烁频率控制，默认0不闪烁
  // outlinePass.pulsePeriod = 2;
  // 模型边缘高亮边框颜色，默认白色
  outlinePass.visibleEdgeColor.set(0xffffff);
  return outlinePass
}

let scene = null
export function createScene(idName) {
  const width = document.getElementById(idName).offsetWidth; // 窗口文档显示区的宽度作为画布宽度
  const height = document.getElementById(idName).offsetHeight; // 窗口文档显示区的高度作为画布高度
  // 记录画布长宽
  const canvasRect = { width, height };
  // 1. 创建一个场景
  scene = new THREE.Scene();
  // 添加坐标系
  // scene.add(new THREE.AxesHelper(400));

  // 2. 添加光源
  createLight(scene);

  // 3. 添加相机
  createCamera(canvasRect);
  // const helper = new THREE.CameraHelper(camera)
  // scene.add(helper)

  // 4. 添加显示标签的父级div
  createLabelRenderer(idName, canvasRect);
  // 5. 渲染
  createRenderer(idName, canvasRect);

  // 6. 移动
  createControls(camera, renderer);

  // 发光效果
  // 1.创建后处理对象EffectComposer，WebGL渲染器作为参数
  const composer = new EffectComposer(renderer);
  // 2.创建一个渲染器通道，场景和相机作为参数
  const renderPass = new RenderPass(scene, camera);
  composer.addPass(renderPass); // 设置renderPass通道
  const outlinePass = createOutlinePass(scene, camera, canvasRect)
  // selectedObjects方法可以设置需要高亮发光描边的模型对象
  // outlinePass.selectedObjects = [mesh]
  // 4.最后把创建好的OutlinePass通道添加到后处理composer中。
  composer.addPass(outlinePass);

  // 7. 渲染循环，即动画效果
  const clock = new THREE.Clock();
  const render = () => {
    const t = clock.getDelta();
    scene.traverse(cube => {
      // 自定义动画
      if (cube.animate) cube.animate(cube, t)
    });
    composer.render();
    // composer.render 与 renderer.render冲突，保留一个即可
    // renderer.render(scene, camera);
    requestAnimationFrame(render);
    controls.update();
    labelRenderer.render(scene, camera);
    TWEEN.update()
  };
  render();

  // 8. 画布跟随窗口变化
  window.onresize = () => {
    const width = document.getElementById(idName).offsetWidth; // 窗口文档显示区的宽度作为画布宽度
    const height = document.getElementById(idName).offsetHeight; // 窗口文档显示区的高度作为画布高度
    renderer.setSize(width, height);
    camera.aspect = width / height;
    labelRenderer.setSize(width, height);
    camera.updateProjectionMatrix();
  };

  return { renderer, scene, camera, outlinePass, controls };
}

// 从父模型中去除符合某些条件的子模型
export function removeMesh(group, fun) {
  const models = group.children.filter(item => fun(item));
  // 移除操作在循环外进行
  models.forEach(model => {
    model.traverse(obj => {
      // 如果模型有材质，也一并删除
      obj.geometry?.dispose();

      if (Array.isArray(obj.material)) {
        obj.material.forEach(item => item.material?.dispose())
      } else {
        obj.material?.dispose();
      }
      // 去除css2dobject标签
      if (obj.isCSS2DObject && obj.element) {
        labelRenderer.domElement.removeChild(obj.element)
      }
    });
    group.remove(model); // 在循环外进行移除
  });
}

// 遍历地图，对地图的地图块的中心点进行添加or移除操作，
// keyName 为需要添加或去除的模型的名称，进行新建模型的方法名称必须为 'create' + keyName(首字母大写)
export function allPointEvent(state, map, size, keyName) {
  map.traverse(item => {
    if (item.self?.point) {
      if (state) {
        // 添加模型
        const mesh = fun['create' + titleCase(keyName)](item.self?.point, size, item.self);
        item.add(mesh);
      } else {
        // 去除模型
        removeMesh(item, cube => cube.name == keyName);
      }
    }
  });
}

// 给定指定经纬度，显示标记点
export function pointEvent(map, points, size, keyName,) {
  const group = new THREE.Group()
  group.name = keyName + 'Group'
  // 去除原有标记点
  removeMesh(map, cube => cube.name == keyName + 'Group');
  points.forEach(item => {
    // 判断该点是否在坐标范围内
    if (IsPtInPoly(item.point, pointList)) {
      // 添加模型
      const mesh = fun['create' + titleCase(keyName)](offsetXY(item.point), size, item);
      mesh.self = { ...item, point: offsetXY(item.point) }
      group.add(mesh)
    }
  })
  map.add(group)
}

export function getDepth(size) {
  const max = Math.max(...size);
  const depth = max / 16;
  return { depth, max }
}

